/**
 * 部署开发包
 * 
 * 输入参数:
 *   ID:        开发包ID
 *   Package:   开发包内容: {Name, Code, Version, Info, Dependencies, Content, Author, ReleaseTime}
 *   Partial:   指示开发包内容是否仅包含部分内容. 值为true时, 仅执行同步逻辑, 不覆盖开发包数据
 * 
 * 输出参数:
 *   true: 部署成功
 */
(function () {
  const WEBROOT = 'wwwroot';

  function get_type(field, dbtype) {
    let type = "";
    switch (field.类型) {
      case 'SZ':
        type = "NUMERIC(" + field.长度 + "," + (field.标度 ?? 0) + ")";
        break;
      case 'RQ':
      case 'SJ':
        switch (dbtype) {
          case 'ORACLE':
            type = "DATE";
            break;
          case 'MYSQL':
          case 'MSSQL':
            type = "DATETIME";
            break;
          case 'SQLITE':
            type = "TEXT";
            break;
          default:
            type = "TIMESTAMP";
            break;
        }
        break;
      case 'WB':
        if (field.长度 === 0) {
          switch (dbtype) {
            case 'ORACLE':
              type = "CLOB";
              break;
            case 'MYSQL':
              type = "LONGTEXT";
              break;
            case 'PGSQL':
            case 'MSSQL':
            default:
              type = "TEXT";
              break;
          }
        } else {
          type = "VARCHAR(" + field.长度 + ")";
        }
        break;
      case 'BJ':
        type = "NUMERIC(1,0)";
        break;
      case 'LS':
        type = "VARCHAR(" + field.长度 + ")";
        break;
      default:
        type = "VARCHAR(32)";
        break;
    }
    return type;
  }

  function get_type_declare(field, dbtype) {
    const type = get_type(field, dbtype);

    const nullable = field.必填 ? " NOT NULL" : " NULL";
    return type + nullable;
  }

  function get_need_modify(field, column, dbtype) {
    switch (field.类型) {
      case 'SZ':
        if (['NUMBER', 'NUMERIC', 'DECIMAL'].includes(column.DATA_TYPE)
          && field.长度 === column.NUMERIC_PRECISION && (field.标度 ?? 0) === column.NUMERIC_SCALE) {
          return '';
        } else {
          return get_type(field, dbtype);
        }
      case 'RQ':
      case 'SJ':
        if (['DATE', 'DATETIME', 'TIMESTAMP', 'TIMESTAMP(6)'].includes(column.DATA_TYPE)) {
          return '';
        } else {
          return get_type(field, dbtype);
        }
      case 'WB':
        if ((['VARCHAR', 'VARCHAR2'].includes(column.DATA_TYPE) && field.长度 === column.CHARACTER_MAXIMUM_LENGTH)
          || (['TEXT', 'LONGTEXT', 'CLOB'].includes(column.DATA_TYPE) && field.长度 === 0)) {
          return '';
        } else {
          return get_type(field, dbtype);
        }
      case 'BJ':
        if (['NUMBER', 'NUMERIC', 'DECIMAL'].includes(column.DATA_TYPE)
          && column.NUMERIC_PRECISION === 1 && column.NUMERIC_SCALE === 0) {
          return '';
        } else {
          return get_type(field, dbtype);
        }
      case 'LS':
        if (['VARCHAR', 'VARCHAR2'].includes(column.DATA_TYPE) && field.长度 === column.CHARACTER_MAXIMUM_LENGTH) {
          return '';
        } else {
          return get_type(field, dbtype);
        }
      default:
        if (['VARCHAR', 'VARCHAR2'].includes(column.DATA_TYPE) && column.CHARACTER_MAXIMUM_LENGTH === 32) {
          return '';
        } else {
          return get_type(field, dbtype);
        }
    }
  }

  function get_need_nullable(field, column) {
    if (Boolean(field.必填) === Boolean(column.NULLABLE === 'N')) {
      return '';
    } else {
      return field.必填 ? "NOT NULL" : "NULL";
    }
  }

  function version_subtract(a, b) {
    const x = b.split('.');
    return a.split('.').map((v, i) => Number(v) - Number(x[i] ?? 0));
  }

  function sync_jobs(id, code, jobs = [], partial) {
    const c_job = A.card.作业;
    A.db.trans('KERNEL', conn => {
      if (!partial) A.db.execute(conn, `DELETE FROM ZY WHERE KFB_ID=?`, [id]);
      jobs.forEach(job => {
        if (!job.ID) throw Error(`作业${job.名称}缺失ID`);
        if (partial) A.db.execute(conn, `DELETE FROM ZY WHERE KFB_ID=? AND MC=?`, [id, job.名称]);
        c_job.save(job);
        delete A.job[(code ? code + '.' : '') + job.名称];
      });
    });
  }

  function sync_pages(id, pages = [], partial) {
    const c_page = A.card.页面;
    A.db.trans('KERNEL', conn => {
      if (!partial) A.db.execute(conn, `DELETE FROM YM WHERE KFB_ID=?`, [id]);
      pages.forEach(page => {
        if (!page.ID) throw Error(`页面${page.名称}缺失ID`);
        c_page.save(page);
      });
    });
  }

  function sync_cards(id, code, cards = [], partial) {
    A.db.trans('KERNEL', conn => {
      if (!partial) A.db.execute(conn, `DELETE FROM KP WHERE KFB_ID=?`, [id]);
      cards.forEach(card => {
        if (card.开发包 != id) throw Error(`卡片${card.名称}的开发包ID无效`);
        if (!card.ID) throw Error(`卡片${card.名称}缺失ID`);
        // 保存卡片模型
        try {
          A.card.卡片.save(card);
        } catch (e) {
          throw Error(card.名称 + ': ' + e.message);
        }
        if (card.数据) {
          A.db.execute(conn, "DELETE FROM KPSJ WHERE KP_ID=?", [card.ID]);
          card.数据.forEach(d => {
            if (!d.ID) throw Error("卡片数据缺失了ID");
            if (d.卡片 != card.ID) throw Error(`卡片${card.名称}的数据${d.名称}的卡片ID无效`);
            // 保存卡片数据
            try {
              A.card.卡片数据.save(d);
            } catch (e) {
              throw Error(`${card.名称}.${d.名称}: ${e.message}`);
            }
          });
        }
        if (card.引用) {
          A.db.execute(conn, "DELETE FROM KPYY WHERE KP_ID=?", [card.ID]);
          card.引用.forEach(d => {
            if (!d.ID) throw Error("卡片引用缺失了ID");
            if (d.卡片 != card.ID) throw Error(`卡片${card.名称}的引用${d.名称}的卡片ID无效`);
            // 保存卡片引用
            try {
              A.card.卡片引用.save(d);
            } catch (e) {
              throw Error(`${card.名称}.${d.名称}: ${e.message}`);
            }
          });
        }
        if (card.索引) {
          A.db.execute(conn, "DELETE FROM KPSY WHERE KP_ID=?", [card.ID]);
          card.索引.forEach(d => {
            if (!d.ID) throw Error("卡片索引缺失了ID");
            if (d.卡片 != card.ID) throw Error(`卡片${card.名称}的索引${d.名称}的卡片ID无效`);
            // 保存卡片索引
            try {
              A.card.卡片索引.save(d);
            } catch (e) {
              throw Error(`${card.名称}.${d.名称}: ${e.message}`);
            }
          });
        }
        delete A.card[(code ? code + '.' : '') + card.名称];
      });
    });

    // 同步表结构
    cards.forEach(card => {
      try {
        A.db.connect(card.连接名称, (conn) => {
          switch (conn.provider) {
            case A.db.provider.SQLITE:
              return sync_card_sqlite(conn, card);
            case A.db.provider.MYSQL:
              return sync_card_mysql(conn, card);
            case A.db.provider.PGSQL:
              return sync_card_pgsql(conn, card);
            case A.db.provider.ORACLE:
              return sync_card_oracle(conn, card);
            case A.db.provider.MSSQL:
              return sync_card_mssql(conn, card);
            case A.db.provider.DM:
              return sync_card_dm(conn, card);
          }
        });
      } catch (e) {
        throw Error(card.名称 + ': ' + e.message);
      }
    });
  }

  function sync_card_oracle(conn, card) {
    const card_comment = card.名称 + (card.描述 ? ": " + card.描述 : '');
    // 检查数据库中是否存在表, 不存在则创建新表,存在的检查每个列
    const { rows: [tbl] } = A.db.execute(conn, "SELECT COMMENTS FROM USER_TAB_COMMENTS WHERE UPPER(TABLE_NAME)=UPPER(?)", [card.标识符]);
    if (!tbl) { // 创建表结构
      // 构造字段列表
      const sql_fields = ["ID VARCHAR(32) NOT NULL PRIMARY KEY"];
      [...card.数据, ...card.引用].forEach(field => sql_fields.push('"' + field.标识符 + '" ' + get_type_declare(field, 'ORACLE')));

      // 建表
      A.db.execute(conn, `CREATE TABLE "${card.标识符}" (${sql_fields.join(',')})`);
      // 注释表和字段
      A.db.execute(conn, `COMMENT ON TABLE "${card.标识符}" IS '${card_comment}'`);
      [...card.数据, ...card.引用].forEach(field => {
        A.db.execute(conn, `COMMENT ON COLUMN "${card.标识符}"."${field.标识符}" IS '${field.名称 + (field.描述 ? ": " + field.描述 : '')}'`);
      });

      // 建立索引
      if (card.索引) {
        card.索引.forEach(idx => {
          A.db.execute(conn, `CREATE ${idx.唯一 ? "UNIQUE " : ""}INDEX ${card.标识符}_${idx.标识符} ON "${card.标识符}" (${idx.字段列表})`);
        });
      }
    } else { // 更新表结构
      const { rows: columns } = A.db.execute(conn, "SELECT COLUMN_NAME,DATA_TYPE,DATA_LENGTH CHARACTER_MAXIMUM_LENGTH,DATA_PRECISION NUMERIC_PRECISION,DATA_SCALE NUMERIC_SCALE,NULLABLE FROM USER_TAB_COLUMNS WHERE UPPER(TABLE_NAME)=UPPER(?)", [card.标识符]);

      // 更新表注释
      if (tbl.COMMENTS !== card_comment) {
        A.db.execute(conn, `COMMENT ON TABLE "${card.标识符}" IS '${card_comment}'`);
      }

      // 建立COLUMN_NAME作为Key的KV结构
      const removed_columns = [];
      const card_column_names = [...card.数据, ...card.引用].map(field => field.标识符.toUpperCase());
      card_column_names.push("ID");
      columns.forEach(c => {
        columns[c.COLUMN_NAME.toUpperCase()] = c;
        if (!card_column_names.includes(c.COLUMN_NAME.toUpperCase())) removed_columns.push(c);
      });

      // 移除的列, 如果是非空的, 需要更新为可空类型
      removed_columns.forEach(col => {
        if (col.NULLABLE === 'N') {
          A.db.execute(conn, `ALTER TABLE "${card.标识符}" MODIFY "${col.COLUMN_NAME}" NULL`);
        }
      });

      // 更新表字段和注释
      [...card.数据, ...card.引用].forEach(field => {
        const column = columns[field.标识符.toUpperCase()];
        if (column) {
          // 存在列, 则检查类型是否一致
          const modify = get_need_modify(field, column, 'ORACLE');
          const nullable = get_need_nullable(field, column);
          if (modify || nullable) {
            A.db.execute(conn, `ALTER TABLE "${card.标识符}" MODIFY "${field.标识符}" ${modify + (nullable ? ' ' + nullable : '')}`);
            A.db.execute(conn, `COMMENT ON COLUMN "${card.标识符}"."${field.标识符}" IS '${field.名称 + (field.描述 ? ": " + field.描述 : '')}'`);
          }
        } else {
          // 增加不存在的列
          A.db.execute(conn, `ALTER TABLE "${card.标识符}" ADD "${field.标识符}" ${get_type_declare(field, 'ORACLE')}`);
          A.db.execute(conn, `COMMENT ON COLUMN "${card.标识符}"."${field.标识符}" IS '${field.名称 + (field.描述 ? ": " + field.描述 : '')}'`);
        }
      });

      // 重建索引.由于遗留索引会占用字段,造成新索引无法创建,所以直接全部删掉再重新添加索引(ID主键的索引除外)
      const { rows } = A.db.execute(conn, "SELECT DISTINCT A.INDEX_NAME FROM USER_INDEXES A, USER_IND_COLUMNS B WHERE A.GENERATED='N' AND UPPER(A.TABLE_NAME)=UPPER(?) AND A.TABLE_NAME=B.TABLE_NAME AND A.INDEX_NAME=B.INDEX_NAME AND B.COLUMN_NAME<>'ID'", [card.标识符]);
      rows.forEach(row => A.db.execute(conn, `DROP INDEX ${row.INDEX_NAME}`));

      if (card.索引) {
        card.索引.forEach(idx => A.db.execute(conn, `CREATE ${idx.唯一 ? "UNIQUE " : ""}INDEX ${card.标识符}_${idx.标识符} ON "${card.标识符}" (${idx.字段列表})`));
      }
    }
  }

  function sync_card_mssql(conn, card) {
    // 检查数据库中是否存在表, 不存在则创建新表,存在的检查每个列
    const { rows: [tables] } = A.db.execute(conn, "SELECT COUNT(*) N FROM INFORMATION_SCHEMA.TABLES WHERE UPPER(TABLE_NAME)=UPPER(?)", [card.标识符]);
    if (tables.N === 0) { // 创建表结构
      // 构造字段列表
      const sql_fields = ["ID VARCHAR(32) NOT NULL PRIMARY KEY"];
      const comments = [];
      [...card.数据, ...card.引用].forEach(field => {
        sql_fields.push('"' + field.标识符 + '" ' + get_type_declare(field, 'MSSQL'));
        comments.push(`EXEC sp_addextendedproperty 'MS_Description','${field.名称 + (field.描述 ? ": " + field.描述 : '')}','SCHEMA','dbo','TABLE','${card.标识符}','COLUMN','${field.标识符}'`);
      });

      // 建表
      A.db.execute(conn, `CREATE TABLE ${card.标识符} (${sql_fields.join(',')})`);
      comments.push(`EXEC sp_addextendedproperty 'MS_Description','${card.名称 + (card.描述 ? ": " + card.描述 : '')}','SCHEMA','dbo','TABLE','${card.标识符}'`);
      comments.forEach(comment => A.db.execute(conn, comment));

      // 建立索引
      if (card.索引) {
        card.索引.forEach(idx => {
          A.db.execute(conn, `CREATE ${(idx.唯一 ? "UNIQUE " : "")}INDEX ${card.标识符}_${idx.标识符} ON "${card.标识符}" (${idx.字段列表})`);
        });
      }
    } else { // 更新表结构
      const { rows: columns } = A.db.execute(conn, "SELECT COLUMN_NAME,CASE WHEN UPPER(DATA_TYPE)='VARCHAR' THEN 'VARCHAR('+CAST(CHARACTER_MAXIMUM_LENGTH AS VARCHAR)+')' WHEN UPPER(DATA_TYPE)='DECIMAL' OR UPPER(DATA_TYPE)='NUMERIC' THEN 'NUMERIC('+CAST(NUMERIC_PRECISION AS VARCHAR)+','+CAST(NUMERIC_SCALE AS VARCHAR)+')' ELSE UPPER(DATA_TYPE) END COLUMN_TYPE,UPPER(DATA_TYPE) DATA_TYPE,CHARACTER_MAXIMUM_LENGTH,NUMERIC_PRECISION,NUMERIC_SCALE,LEFT(IS_NULLABLE,1) NULLABLE FROM INFORMATION_SCHEMA.COLUMNS WHERE UPPER(TABLE_NAME)=UPPER(?)", [card.标识符]);

      const removed_columns = [];
      const card_column_names = [...card.数据, ...card.引用].map(field => field.标识符.toUpperCase());
      card_column_names.push("ID");
      columns.forEach(c => {
        columns[c.COLUMN_NAME.toUpperCase()] = c;
        if (!card_column_names.includes(c.COLUMN_NAME.toUpperCase())) removed_columns.push(c);
      });

      // 移除的列, 如果是非空的, 需要更新为可空类型
      removed_columns.forEach(col => {
        if (col.NULLABLE === 'N') {
          A.db.execute(conn, `ALTER TABLE "${card.标识符}" ALTER COLUMN "${col.COLUMN_NAME}" ${col.COLUMN_TYPE} NULL`);
        }
      });

      // 更新表字段和注释
      const comments = [];
      [...card.数据, ...card.引用].forEach(field => {
        const column = columns[field.标识符.toUpperCase()];
        if (column) {
          // 存在列, 则检查类型是否一致
          const modify = get_need_modify(field, column, 'MSSQL');
          const nullable = get_need_nullable(field, column);
          if (modify || nullable) {
            A.db.execute(conn, `ALTER TABLE "${card.标识符}" ALTER COLUMN "${field.标识符}" ${get_type_declare(field, 'MSSQL')}`);
          }
          comments.push(`EXEC sp_updateextendedproperty 'MS_Description','${field.名称 + (field.描述 ? ": " + field.描述 : '')}','SCHEMA','dbo','TABLE','${card.标识符}','COLUMN','${field.标识符}'`);
        } else {
          A.db.execute(conn, `ALTER TABLE "${card.标识符}" ADD "${field.标识符}" ${get_type_declare(field, 'MSSQL')}`);
          comments.push(`EXEC sp_addextendedproperty 'MS_Description','${field.名称 + (field.描述 ? ": " + field.描述 : '')}','SCHEMA','dbo','TABLE','${card.标识符}','COLUMN','${field.标识符}'`);
        }
      });
      comments.push(`EXEC sp_updateextendedproperty 'MS_Description','${card.名称 + (card.描述 ? ": " + card.描述 : '')}','SCHEMA','dbo','TABLE','${card.标识符}'`);
      comments.forEach(comment => A.db.execute(conn, comment));

      // 重建索引.由于遗留索引会占用字段,造成新索引无法创建,所以直接全部删掉再重新添加索引(主键除外)
      const { rows } = A.db.execute(conn, "SELECT A.NAME FROM SYS.INDEXES A, SYS.TABLES B WHERE A.OBJECT_ID=B.OBJECT_ID AND A.IS_PRIMARY_KEY=0 AND A.NAME IS NOT NULL AND UPPER(B.NAME)=UPPER(?)", [card.标识符]);
      rows.forEach(row => A.db.execute(conn, "DROP INDEX " + row.NAME + ' ON "' + card.标识符 + '"'));

      if (card.索引) {
        card.索引.forEach(idx => {
          A.db.execute(conn, `CREATE ${idx.唯一 ? "UNIQUE " : ""}INDEX ${card.标识符 + "_" + idx.标识符} ON "${card.标识符}" (${idx.字段列表})`);
        });
      }
    }
  }

  function sync_card_mysql(conn, card) {
    const card_comment = card.名称 + (card.描述 ? ": " + card.描述 : '');
    // 检查数据库中是否存在表, 不存在则创建新表,存在的检查每个列
    const { rows: [{ DB_NAME }] } = A.db.execute(conn, "SELECT DATABASE() DB_NAME");
    const { rows: [tbl] } = A.db.execute(conn, "SELECT TABLE_COMMENT FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA=? AND UPPER(TABLE_NAME)=UPPER(?)", [DB_NAME, card.标识符]);
    if (!tbl) { // 创建表结构
      // 构造字段列表
      const sql_fields = ["ID VARCHAR(32) NOT NULL PRIMARY KEY"];
      [...card.数据, ...card.引用].forEach(field => {
        let txt = '"' + field.标识符 + '" ' + get_type_declare(field, 'MYSQL');
        txt += " COMMENT '" + field.名称 + (field.描述 ? ": " + field.描述 : '') + "'";
        sql_fields.push(txt);
      });

      // 建表
      A.db.execute(conn, `CREATE TABLE \`${card.标识符}\` (${sql_fields.join(',')}) COMMENT='${card_comment}'`);

      // 建立索引
      if (card.索引) {
        card.索引.forEach(idx => {
          A.db.execute(conn, `CREATE ${(idx.唯一 ? "UNIQUE " : "")}INDEX ${card.标识符}_${idx.标识符} ON \`${card.标识符}\` (${idx.字段列表})`);
        });
      }
    } else { // 更新表结构
      const { rows: columns } = A.db.execute(conn, "SELECT COLUMN_NAME,UPPER(COLUMN_TYPE) COLUMN_TYPE,UPPER(DATA_TYPE) DATA_TYPE,CHARACTER_MAXIMUM_LENGTH,NUMERIC_PRECISION,NUMERIC_SCALE,LEFT(IS_NULLABLE,1) NULLABLE,COLUMN_COMMENT FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA=? AND UPPER(TABLE_NAME)=UPPER(?)", [DB_NAME, card.标识符]);

      if (tbl.TABLE_COMMENT !== card_comment) {
        A.db.execute(conn, `ALTER TABLE \`${card.标识符}\` COMMENT '${card_comment}'`);
      }

      const removed_columns = [];
      const card_column_names = [...card.数据, ...card.引用].map(field => field.标识符.toUpperCase());
      card_column_names.push("ID");
      columns.forEach(c => {
        columns[c.COLUMN_NAME.toUpperCase()] = c;
        if (!card_column_names.includes(c.COLUMN_NAME.toUpperCase())) removed_columns.push(c);
      });

      // 移除的列, 更新为可空类型
      removed_columns.forEach(col => {
        if (col.NULLABLE === 'N') {
          A.db.execute(conn, `ALTER TABLE \`${card.标识符}\` MODIFY COLUMN \`${col.COLUMN_NAME}\` ${col.COLUMN_TYPE} NULL`);
        }
      });

      // 更新表字段和注释
      [...card.数据, ...card.引用].forEach(field => {
        const column = columns[field.标识符.toUpperCase()];
        const field_comment = field.名称 + (field.描述 ? ": " + field.描述 : '');
        if (column) {
          // 存在列, 则检查类型是否一致
          const modify = get_need_modify(field, column, 'MYSQL');
          const nullable = get_need_nullable(field, column);
          if (modify || nullable || field_comment !== column.COLUMN_COMMENT) {
            A.db.execute(conn, `ALTER TABLE \`${card.标识符}\` MODIFY COLUMN \`${field.标识符}\` ${get_type_declare(field, 'MYSQL')} COMMENT '${field_comment}'`);
          }
        } else {
          A.db.execute(conn, `ALTER TABLE \`${card.标识符}\` ADD COLUMN \`${field.标识符}\` ${get_type_declare(field, 'MYSQL')} COMMENT '${field_comment}'`);
        }
      });

      // 重建索引.由于遗留索引会占用字段,造成新索引无法创建,所以直接全部删掉再重新添加索引(主键除外)
      const { rows } = A.db.execute(conn, "SELECT DISTINCT INDEX_NAME FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA=? AND UPPER(TABLE_NAME)=UPPER(?) AND INDEX_NAME<>'PRIMARY'", [DB_NAME, card.标识符]);
      rows.forEach(row => A.db.execute(conn, `DROP INDEX ${row.INDEX_NAME} ON ` + card.标识符));

      if (card.索引) {
        card.索引.forEach(idx => {
          A.db.execute(conn, `CREATE ${(idx.唯一 ? "UNIQUE " : "")}INDEX ${card.标识符}_${idx.标识符} ON \`${card.标识符}\` (${idx.字段列表})`);
        });
      }
    }
  }

  function sync_card_pgsql(conn, card) {
    // 检查数据库中是否存在表, 不存在则创建新表,存在的检查每个列
    const { rows: [tables] } = A.db.execute(conn, "SELECT COUNT(*) N FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='public' AND UPPER(TABLE_NAME)=UPPER(?)", [card.标识符]);
    if (tables.N === 0) { // 创建表结构
      // 构造字段列表
      const sql_fields = ["ID VARCHAR(32) NOT NULL PRIMARY KEY"];
      [...card.数据, ...card.引用].forEach(field => sql_fields.push('"' + field.标识符 + '" ' + get_type_declare(field, 'PGSQL')));

      // 建表
      A.db.execute(conn, `CREATE TABLE "${card.标识符}" (${sql_fields.join(',')})`);
      // 注释表和字段
      A.db.execute(conn, `COMMENT ON TABLE "${card.标识符}" IS '${card.名称 + (card.描述 ? ": " + card.描述 : '')}'`);
      [...card.数据, ...card.引用].forEach(field => {
        A.db.execute(conn, `COMMENT ON COLUMN "${card.标识符}"."${field.标识符}" IS '${field.名称 + (field.描述 ? ": " + field.描述 : '')}'`);
      });

      // 建立索引
      if (card.索引) {
        card.索引.forEach(idx => {
          A.db.execute(conn, `CREATE ${(idx.唯一 ? "UNIQUE " : "")}INDEX ${card.标识符}_${idx.标识符} ON ${card.标识符} (${idx.字段列表})`);
        });
      }
    } else { // 更新表结构
      const { rows: columns } = A.db.execute(conn, "SELECT COLUMN_NAME,CASE WHEN UPPER(UDT_NAME)='VARCHAR' THEN 'VARCHAR(' || CAST(CHARACTER_MAXIMUM_LENGTH AS VARCHAR) || ')' WHEN UPPER(UDT_NAME)='DECIMAL' OR UPPER(UDT_NAME)='NUMERIC' THEN 'NUMERIC(' || CAST(NUMERIC_PRECISION AS VARCHAR) || ',' || CAST(NUMERIC_SCALE AS VARCHAR) || ')' ELSE UPPER(UDT_NAME) END COLUMN_TYPE,UPPER(UDT_NAME) DATA_TYPE,CHARACTER_MAXIMUM_LENGTH,NUMERIC_PRECISION,NUMERIC_SCALE,LEFT(IS_NULLABLE,1) NULLABLE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA='public' AND UPPER(TABLE_NAME)=UPPER(?)", [card.标识符]);

      const removed_columns = [];
      const card_column_names = [...card.数据, ...card.引用].map(field => field.标识符.toUpperCase());
      card_column_names.push("ID");
      columns.forEach(c => {
        columns[c.COLUMN_NAME.toUpperCase()] = c;
        if (!card_column_names.includes(c.COLUMN_NAME.toUpperCase())) removed_columns.push(c);
      });

      // 移除的列, 更新为可空类型
      removed_columns.forEach(col => {
        if (col.NULLABLE === 'N') {
          A.db.execute(conn, `ALTER TABLE "${card.标识符}" ALTER COLUMN "${col.COLUMN_NAME}" DROP NOT NULL`);
        }
      });

      // 更新表字段和注释
      [...card.数据, ...card.引用].forEach(field => {
        const column = columns[field.标识符.toUpperCase()];
        if (column) {
          const modify = get_need_modify(field, column, 'PGSQL');
          const nullable = get_need_nullable(field, column);
          if (modify) {
            A.db.execute(conn, `ALTER TABLE "${card.标识符}" ALTER COLUMN "${field.标识符}" TYPE ${modify}`);
          }
          if (nullable) {
            A.db.execute(conn, `ALTER TABLE "${card.标识符}" ALTER COLUMN "${field.标识符}" ${nullable === 'NOT NULL' ? 'SET NOT NULL' : 'DROP NOT NULL'}`);
          }
        } else {
          A.db.execute(conn, `ALTER TABLE "${card.标识符}" ADD COLUMN "${field.标识符}" ${get_type_declare(field, 'PGSQL')}`);
        }
        A.db.execute(conn, `COMMENT ON COLUMN "${card.标识符}"."${field.标识符}" IS '${field.名称 + (field.描述 ? ": " + field.描述 : '')}'`);
      });
      A.db.execute(conn, `COMMENT ON TABLE "${card.标识符}" IS '${card.名称 + (card.描述 ? ": " + card.描述 : '')}'`);

      // 重建索引.由于遗留索引会占用字段,造成新索引无法创建,所以直接全部删掉再重新添加索引(主键除外)
      const { rows } = A.db.execute(conn, "SELECT I.RELNAME AS INDEXNAME FROM PG_INDEX X JOIN PG_CLASS C ON C.OID = X.INDRELID JOIN PG_CLASS I ON I.OID = X.INDEXRELID LEFT JOIN PG_NAMESPACE N ON N.OID = C.RELNAMESPACE LEFT JOIN PG_TABLESPACE T ON T.OID = I.RELTABLESPACE WHERE N.NSPNAME='public' AND UPPER(C.RELNAME)=UPPER(?) AND X.INDISPRIMARY='f'", [card.标识符]);
      rows.forEach(row => A.db.execute(conn, "DROP INDEX " + row.INDEXNAME));

      if (card.索引) {
        card.索引.forEach(idx => {
          A.db.execute(conn, `CREATE ${(idx.唯一 ? "UNIQUE " : "")}INDEX ${card.标识符}_${idx.标识符} ON "${card.标识符}" (${idx.字段列表})`);
        });
      }
    }
  }

  function sync_card_sqlite(conn, card) {
    // 检查数据库中是否存在表, 不存在则创建新表,存在的检查每个列
    const { rows: [tables] } = A.db.execute(conn, "SELECT COUNT(*) N FROM SQLITE_MASTER WHERE TYPE='table' AND UPPER(NAME)=UPPER(?)", [card.标识符]);
    if (tables.N === 0) { // 创建表结构
      // 构造字段列表
      const sql_fields = ["ID VARCHAR(32) NOT NULL PRIMARY KEY"];
      [...card.数据, ...card.引用].forEach(field => sql_fields.push('"' + field.标识符 + '" ' + get_type_declare(field, 'SQLITE')));

      // 建表
      A.db.execute(conn, `CREATE TABLE "${card.标识符}" (${sql_fields.join(',')})`);

      // 建立索引
      if (card.索引) {
        card.索引.forEach(idx => {
          A.db.execute(conn, `CREATE ${(idx.唯一 ? "UNIQUE " : "")}INDEX ${card.标识符}_${idx.标识符} ON "${card.标识符}" (${idx.字段列表})`);
        });
      }
    } else { // 更新表结构
      const { rows: columns } = A.db.execute(conn, "PRAGMA TABLE_INFO(" + card.标识符 + ")");

      const removed_columns = [];
      const card_column_names = [...card.数据, ...card.引用].map(field => field.标识符.toUpperCase());
      card_column_names.push("ID");
      columns.forEach(c => {
        columns[c.NAME.toUpperCase()] = c;
        if (!card_column_names.includes(c.NAME.toUpperCase())) removed_columns.push(c);
      });

      // SQLite暂时还不支持修改字段类型. 因此移除的列直接去掉
      removed_columns.forEach(col => {
        A.db.execute(conn, `ALTER TABLE "${card.标识符}" DROP COLUMN "${col.NAME}"`);
      });

      // 更新表字段和注释
      [...card.数据, ...card.引用].forEach(field => {
        const column = columns[field.标识符.toUpperCase()];
        if (!column) {
          A.db.execute(conn, `ALTER TABLE "${card.标识符}" ADD COLUMN "${field.标识符}" ` + get_type_declare(field, 'SQLITE'));
        } else {
          // SQLite暂时还不支持修改字段类型(也无需修改)
        }
      });

      // 重建索引.由于遗留索引会占用字段,造成新索引无法创建,所以直接全部删掉再重新添加索引(主键除外)
      const { rows } = A.db.execute(conn, "SELECT NAME FROM SQLITE_MASTER WHERE TYPE='index' AND UPPER(TBL_NAME)=UPPER(?) AND SQL IS NOT NULL", [card.标识符]);
      rows.forEach(row => A.db.execute(conn, "DROP INDEX " + row.NAME));

      if (card.索引) {
        card.索引.forEach(idx => {
          A.db.execute(conn, `CREATE ${(idx.唯一 ? "UNIQUE " : "")}INDEX ${card.标识符}_${idx.标识符} ON "${card.标识符}" (${idx.字段列表})`);
        });
      }
    }
  }

  function sync_card_dm(conn, card) {
    const card_comment = card.名称 + (card.描述 ? ": " + card.描述 : '');
    // 检查数据库中是否存在表, 不存在则创建新表,存在的检查每个列
    const { rows: [tbl] } = A.db.execute(conn, 'SELECT "COMMENT$" COMMENTS FROM SYSTABLECOMMENTS WHERE UPPER(TVNAME)=UPPER(?)', [card.标识符]);
    if (!tbl) { // 创建表结构
      // 构造字段列表
      const sql_fields = ["ID VARCHAR(32) NOT NULL PRIMARY KEY"];
      [...card.数据, ...card.引用].forEach(field => sql_fields.push('"' + field.标识符 + '" ' + get_type_declare(field, 'ORACLE')));

      // 建表
      A.db.execute(conn, `CREATE TABLE "${card.标识符}" (${sql_fields.join(',')})`);
      // 注释表和字段
      A.db.execute(conn, `COMMENT ON TABLE "${card.标识符}" IS '${card_comment}'`);
      [...card.数据, ...card.引用].forEach(field => {
        A.db.execute(conn, `COMMENT ON COLUMN "${card.标识符}"."${field.标识符}" IS '${field.名称 + (field.描述 ? ": " + field.描述 : '')}'`);
      });

      // 建立索引
      if (card.索引) {
        card.索引.forEach(idx => {
          A.db.execute(conn, `CREATE ${idx.唯一 ? "UNIQUE " : ""}INDEX ${card.标识符}_${idx.标识符} ON "${card.标识符}" (${idx.字段列表})`);
        });
      }
    } else { // 更新表结构
      const { rows: columns } = A.db.execute(conn, "SELECT COLUMN_NAME,DATA_TYPE,DATA_LENGTH CHARACTER_MAXIMUM_LENGTH,DATA_PRECISION NUMERIC_PRECISION,DATA_SCALE NUMERIC_SCALE,NULLABLE FROM ALL_TAB_COLUMNS WHERE UPPER(TABLE_NAME)=UPPER(?)", [card.标识符]);

      // 更新表注释
      if (tbl.COMMENTS !== card_comment) {
        A.db.execute(conn, `COMMENT ON TABLE "${card.标识符}" IS '${card_comment}'`);
      }

      // 建立COLUMN_NAME作为Key的KV结构
      const removed_columns = [];
      const card_column_names = [...card.数据, ...card.引用].map(field => field.标识符.toUpperCase());
      card_column_names.push("ID");
      columns.forEach(c => {
        columns[c.COLUMN_NAME.toUpperCase()] = c;
        if (!card_column_names.includes(c.COLUMN_NAME.toUpperCase())) removed_columns.push(c);
      });

      // 移除的列, 如果是非空的, 需要更新为可空类型
      removed_columns.forEach(col => {
        if (col.NULLABLE === 'N') {
          A.db.execute(conn, `ALTER TABLE "${card.标识符}" MODIFY "${col.COLUMN_NAME}" NULL`);
        }
      });

      // 更新表字段和注释
      [...card.数据, ...card.引用].forEach(field => {
        const column = columns[field.标识符.toUpperCase()];
        if (column) {
          // 存在列, 则检查类型是否一致
          const modify = get_need_modify(field, column, 'ORACLE');
          const nullable = get_need_nullable(field, column);
          if (modify || nullable) {
            A.db.execute(conn, `ALTER TABLE "${card.标识符}" MODIFY "${field.标识符}" ${modify + (nullable ? ' ' + nullable : '')}`);
            A.db.execute(conn, `COMMENT ON COLUMN "${card.标识符}"."${field.标识符}" IS '${field.名称 + (field.描述 ? ": " + field.描述 : '')}'`);
          }
        } else {
          // 增加不存在的列
          A.db.execute(conn, `ALTER TABLE "${card.标识符}" ADD "${field.标识符}" ${get_type_declare(field, 'ORACLE')}`);
          A.db.execute(conn, `COMMENT ON COLUMN "${card.标识符}"."${field.标识符}" IS '${field.名称 + (field.描述 ? ": " + field.描述 : '')}'`);
        }
      });

      // 重建索引.由于遗留索引会占用字段,造成新索引无法创建,所以直接全部删掉再重新添加索引(主键除外)
      const { rows } = A.db.execute(conn, "SELECT DISTINCT INDEX_NAME FROM ALL_IND_COLUMNS WHERE COLUMN_NAME<>'ID' and UPPER(TABLE_NAME)=UPPER(?)", [card.标识符]);
      rows.forEach(row => A.db.execute(conn, `DROP INDEX ${row.INDEX_NAME}`));

      if (card.索引) {
        card.索引.forEach(idx => A.db.execute(conn, `CREATE ${idx.唯一 ? "UNIQUE " : ""}INDEX ${card.标识符}_${idx.标识符} ON ${card.标识符} (${idx.字段列表})`));
      }
    }
  }

  function sync_data(id, data_set = {}) {
    Object.keys(data_set).forEach(card_name => {
      try {
        let card = A.card[card_name];
        let records = data_set[card_name];
        records.forEach(record => record.__DELETED__ ? card.delete(record) : card.save(record));
      } catch (e) {
        throw Error(card_name + ': ' + e.message);
      }
    });
  }

  function sync_web(Name, Entrypoints, Zip) {
    const package_folder = A.__clr.System.IO.Path.Combine(App.Environment.ContentRootPath, WEBROOT, Name);

    // 先释放压缩包文件
    const ZipPath = A.__clr.System.IO.Path.Combine(A.__clr.System.IO.Directory.GetCurrentDirectory(), 'docs', Zip);
    const stream = A.__clr.System.IO.File.Open(ZipPath, A.__clr.System.IO.FileMode.Open);
    const archive = Host.newObj(A.__clr.System.IO.Compression.ZipArchive, stream);
    for (let i = 0; i < archive.Entries.Count; i++) {
      const entry = archive.Entries[i];
      if (entry.FullName.endsWith('/')) continue;
      const destinationPath = A.__clr.System.IO.Path.Combine(package_folder, entry.FullName.replaceAll('\\', '/'));
      const FI = Host.newObj(A.__clr.System.IO.FileInfo, destinationPath);
      try {
        // 创建不存在的目录
        const DI = FI.Directory;
        if (!DI.Exists) DI.Create();
        // 写入文件
        const reader = entry.Open();
        const writer = FI.OpenWrite();
        reader.CopyTo(writer);
        writer.Close();
      } catch (e) {
        throw Error(entry.FullName + ': ' + e.message);
      }
    }
    stream.Close();

    // 后更新入口信息
    if (Entrypoints) {
      const entrypoints = A.__clr.System.IO.Path.Combine(App.Environment.ContentRootPath, WEBROOT, "entrypoints.json");
      const txt = A.__clr.System.IO.File.Exists(entrypoints) ? A.fs.readAllText(entrypoints) : "{}";
      const json = JSON.parse(txt);
      json[Name] = Entrypoints;
      A.fs.writeAllText(entrypoints, JSON.stringify(json));
    }
  }

  return function ({ UID, ID, Package, Partial = false }) {
    if (!UID) throw Error("未授权的访问");
    let { rows: lps } = A.db.execute('KERNEL', "SELECT ID FROM BSSQ WHERE LP=? AND JULIANDAY(DATETIME('NOW', 'LOCALTIME'))-JULIANDAY(CJSJ)<=YXQ", [UID]);
    if (lps.length === 0) throw Error("授权令牌无效");

    let { Name, Code, Version, Info, Dependencies, Content, Author, ReleaseTime, Entrypoints, Zip } = Package;

    // 检查依赖项是否满足
    if (Dependencies) {
      Dependencies.forEach(({ ID, Name, Version }) => {
        let { rows } = A.db.execute('KERNEL', "SELECT ID,BBH FROM KFB WHERE ID=?", [ID]);
        if (rows.length === 0) throw Error(`依赖的${Name}包不存在`);
        let [main, major] = version_subtract(rows[0].BBH, Version);
        if (main != 0 || major < 0) throw Error(`依赖的${Name}包版本低于${Version}`);
      });
    }

    // 同步内容
    if (Content) {
      // 先保存开发包
      let content = JSON.stringify(Content);
      let depend = Dependencies ? JSON.stringify(Dependencies) : null;
      let { rows } = A.db.execute('KERNEL', "SELECT ID FROM KFB WHERE ID=?", [ID]);
      if (rows.length === 0) {
        // 写入开发包记录
        A.db.execute('KERNEL', "INSERT INTO KFB (ID, MC, DM, BBH, BBMS, NR, YL, FBR, FBSJ) VALUES (?,?,?,?,?,?,?,?,?)",
          [ID, Name, Code, Version, Info, content, depend, Author, ReleaseTime]);
      } else if (!Partial) { // 完整包才需要更新
        A.db.execute('KERNEL', "UPDATE KFB SET MC=?,DM=?,BBH=?,BBMS=?,NR=?,YL=?,FBR=?,FBSJ=? WHERE ID=?",
          [Name, Code, Version, Info, content, depend, Author, ReleaseTime, ID]);
      }

      let { Cards, Jobs, Pages, DataSet } = Content;
      sync_cards(ID, Code, Cards, Partial);
      sync_jobs(ID, Code, Jobs, Partial);
      sync_pages(ID, Pages, Partial);
      sync_data(ID, DataSet);
    }

    // 更新前端包
    if (Zip) sync_web(Name, Entrypoints, Zip);

    return true;
  };
})()